using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;

namespace UDS.Management.Data.Package
{
    internal class ResourceDBPackage : IDBPackage
    {
        /// <summary>
        /// The database manifest
        /// </summary>
        private DBManifestDescription manifestDesc;
        private bool needsCleanup = false;

        /// <summary>
        /// Create a new resource database package
        /// </summary>
        public ResourceDBPackage(DBManifestDescription manifestDesc)
        {
            this.manifestDesc = manifestDesc;
            // Get a manifest helper to help with the loading and validation
            using (ManifestHelper manifestLoader = new ManifestHelper(manifestDesc.assembly.GetName().Name))
            {
                using (Stream ManifestStream = manifestDesc.assembly.GetManifestResourceStream(manifestDesc.manifestResourceName))
                {
                    manifest = manifestLoader.Load(ManifestStream);
                }
            }
            foreach (SqlSchemaChangeFamily family in this.GetSchemaChanges())
            {
                if (family.SchemaCleanupChanges != null)
                {
                    needsCleanup = true;
                    break;
                }
            }
        }

        #region Internal classes
        ///////////////////////////////////////////////
        //            
        ///////////////////////////////////////////////
        //StoredProcedureFileReaderEnumerator
        ///////////////////////////////////////////////
        internal class StoredProcedureFileReaderEnumerable<T> : IEnumerable<T> where T : ScriptSource, new()
        {
            private List<string> m_files;
            private ResourceDBPackage parent;

            public StoredProcedureFileReaderEnumerable(List<string> Files, ResourceDBPackage parent)
            {
                this.parent = parent;
                m_files = Files;
            }

            #region IEnumerable<SqlSource> Members

            public IEnumerator<T> GetEnumerator()
            {
                return new StoredProcedureFileReaderEnumerator<T>(m_files, parent);
            }

            #endregion

            #region IEnumerable Members

            System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
            {
                return GetEnumerator();
            }

            #endregion
        }

        internal class StoredProcedureFileReaderEnumerator<T> : IEnumerator<T> where T : ScriptSource, new()
        {
            private List<string> m_files;
            private List<string>.Enumerator m_fileEnumerator;
            private ResourceDBPackage parent;

            public StoredProcedureFileReaderEnumerator(List<string> files, ResourceDBPackage parent)
            {
                this.m_files = files;
                m_fileEnumerator = m_files.GetEnumerator();
                this.parent = parent;
                Reset();
            }

            #region IEnumerator<SqlSource> Members

            public T Current
            {
                get
                {
                    string currentFile = m_fileEnumerator.Current;
                    T source = new T();
                    source.Initialize(currentFile, parent.GetResourceStream(parent.GetFileInfo(currentFile)));
                    return source;
                }
            }

            #endregion

            #region IDisposable Members

            public void Dispose()
            {
            }

            #endregion

            #region IEnumerator Members

            object System.Collections.IEnumerator.Current
            {
                get
                {
                    return this.Current;
                }
            }

            public bool MoveNext()
            {
                return m_fileEnumerator.MoveNext();
            }

            public void Reset()
            {
                ((IEnumerator)m_fileEnumerator).Reset();
            }

            #endregion
        }

        ////////////////////////////////////////////

        internal class ResourceFileReaderEnumerable<T> : IEnumerable<T> where T : ScriptSource, new()
        {
            private List<string> m_files;
            private ResourceDBPackage parent;

            public ResourceFileReaderEnumerable(List<string> Files, ResourceDBPackage parent)
            {
                this.parent = parent;
                m_files = Files;
            }

            #region IEnumerable<SqlSource> Members

            public IEnumerator<T> GetEnumerator()
            {
                return new ResourceFileReaderEnumerator<T>(m_files, parent);
            }

            #endregion

            #region IEnumerable Members

            System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
            {
                return GetEnumerator();
            }

            #endregion
        }

        internal class ResourceFileReaderEnumerator<T> : IEnumerator<T> where T : ScriptSource, new()
        {
            private List<string> m_files;
            private List<string>.Enumerator m_fileEnumerator;
            private ResourceDBPackage parent;

            public ResourceFileReaderEnumerator(List<string> files, ResourceDBPackage parent)
            {
                this.m_files = files;
                m_fileEnumerator = m_files.GetEnumerator();
                this.parent = parent;
                Reset();
            }

            #region IEnumerator<SqlSource> Members

            public T Current
            {
                get
                {
                    string currentFile = m_fileEnumerator.Current;
                    T source = new T();
                    source.Initialize(currentFile, parent.GetResourceStream(currentFile));
                    return source;
                }
            }

            #endregion

            #region IDisposable Members

            public void Dispose()
            {
            }

            #endregion

            #region IEnumerator Members

            object System.Collections.IEnumerator.Current
            {
                get
                {
                    return this.Current;
                }
            }

            public bool MoveNext()
            {
                return m_fileEnumerator.MoveNext();
            }

            public void Reset()
            {
                ((IEnumerator)m_fileEnumerator).Reset();
            }

            #endregion
        }

        internal class ResourceSchemaFamilyEnumerable : IEnumerable<SqlSchemaChangeFamily>
        {
            private ResourceDBPackage parent;

            public ResourceSchemaFamilyEnumerable(ResourceDBPackage parent)
            {
                this.parent = parent;
            }

            #region IEnumerable<SqlSchemaChangeFamily> Members

            public IEnumerator<SqlSchemaChangeFamily> GetEnumerator()
            {
                return new ResourceSchemaFamilyEnumerator(parent);
            }

            #endregion

            #region IEnumerable Members

            IEnumerator IEnumerable.GetEnumerator()
            {
                return GetEnumerator();
            }

            #endregion
        }

        internal class ResourceSchemaFamilyEnumerator : IEnumerator<SqlSchemaChangeFamily>
        {
            private SchemaChangeFamily[] m_changeFamilies;
            private int m_currentFamilyIndex;
            private ResourceDBPackage parent;

            internal ResourceSchemaFamilyEnumerator(ResourceDBPackage parent)
            {
                this.parent = parent;
                this.m_changeFamilies = parent.Manifest.Schema;

                Reset();
            }

            #region IEnumerator<SqlSchemaChangeFamily> Members

            public SqlSchemaChangeFamily Current
            {
                get
                {
                    if (m_changeFamilies != null)
                    {
                        SchemaChangeFamily Family = m_changeFamilies[m_currentFamilyIndex];
                        ResourceSchemaChangeEnumerable updateEnumerable = new ResourceSchemaChangeEnumerable(parent, Family.Update);
                        ResourceSchemaChangeEnumerable cleanupEnumerable = null;
                        if (Family.Cleanup != null)
                        {
                            cleanupEnumerable = new ResourceSchemaChangeEnumerable(parent, Family.Cleanup);
                        }
                        SqlSchemaChangeFamily SqlFamilyChange = new SqlSchemaChangeFamily(Family, updateEnumerable, cleanupEnumerable);

                        return SqlFamilyChange;
                    }

                    return null;
                }
            }

            #endregion

            #region IDisposable Members

            public void Dispose()
            {
            }

            #endregion

            #region IEnumerator Members

            object IEnumerator.Current
            {
                get { return this.Current; }
            }

            public bool MoveNext()
            {
                if (m_changeFamilies == null)
                {
                    // Family array is empty
                    return false;
                }

                if (m_currentFamilyIndex >= this.m_changeFamilies.Length - 1)
                    return false;

                ++m_currentFamilyIndex;
                return true;
            }

            public void Reset()
            {
                m_currentFamilyIndex = -1;
            }

            #endregion
        }

        internal class ResourceSchemaChangeEnumerable : IEnumerable<SqlSchemaChange>
        {
            private ResourceDBPackage parent;
            private SchemaChange[] schemaChanges;

            public ResourceSchemaChangeEnumerable(ResourceDBPackage parent, SchemaChange[] schemaChanges)
            {
                this.parent = parent;
                this.schemaChanges = schemaChanges;
            }

            #region IEnumerable<SqlSchemaChange> Members

            public IEnumerator<SqlSchemaChange> GetEnumerator()
            {
                return new SchemaChangeEnumerator(parent, schemaChanges);
            }

            #endregion

            #region IEnumerable Members

            IEnumerator IEnumerable.GetEnumerator()
            {
                return this.GetEnumerator();
            }

            #endregion
        }

        internal class SchemaChangeEnumerator : IEnumerator<SqlSchemaChange>
        {
            private ResourceDBPackage parent;
            private SchemaChange[] schemaChanges;
            private int m_currentChangeIndex;

            public SchemaChangeEnumerator(ResourceDBPackage parent, SchemaChange[] schemaChanges)
            {
                this.parent = parent;
                this.schemaChanges = schemaChanges;
                Reset();
            }

            #region IEnumerator<SqlSchemaChange> Members

            public SqlSchemaChange Current
            {
                get
                {
                    if (schemaChanges != null)
                    {
                        SchemaChange change = schemaChanges[m_currentChangeIndex];

                        List<SqlSource> SourcesList = new List<SqlSource>();

                        // Allow schema changes with no files
                        //if (change.File == null)
                        //{
                        //    throw new Exceptions.DatabaseManagementException(
                        //    String.Format("No files specified for change {0}", change.Id.ToString()));
                        //}

                        // All schema changes are files.
                        // Directories are not allowed
                        if (change.File != null)
                        {
                            foreach (string Location in change.File)
                            {
                                if (String.IsNullOrEmpty(Location))
                                    throw new Exceptions.DatabaseManagementException("An invalid schema change location was specified");

                                // Need to look up the file info from the list of files
                                string Info = parent.GetFileInfo(Location);

                                // Now read the content from the assembly
                                if (Info == null)
                                {
                                    throw new Exceptions.DatabaseManagementException(
                                        String.Format("Resource {0} could not be found", Location)
                                     );
                                }
                                StreamReader contentStream = new StreamReader(parent.GetResourceStream(Info));

                                SourcesList.Add(new SqlSource(Info, contentStream));
                            }
                        }

                        return new SqlSchemaChange(change.Id, change.Description, SourcesList.ToArray());
                    }
                    return null;
                }
            }

            #endregion

            #region IDisposable Members

            public void Dispose()
            {
                // Do nothing
            }

            #endregion

            #region IEnumerator Members

            object IEnumerator.Current
            {
                get
                {
                    return this.Current;
                }
            }

            public bool MoveNext()
            {
                if (schemaChanges == null)
                {
                    // Family array is empty
                    return false;
                }

                if (m_currentChangeIndex >= schemaChanges.Length - 1)
                    return false;

                ++m_currentChangeIndex;

                return true;
            }

            public void Reset()
            {
                m_currentChangeIndex = -1;
            }

            #endregion
        }

        #endregion

        /// <summary>
        /// This is copied twice in here.  Need a better way
        /// to search for the files needed from the list of resource files.
        /// </summary>
        /// <param name="Name"></param>
        /// <returns></returns>
        private string GetFileInfo(string name)
        {
            return manifestDesc.catalog.GetFileResourceName(manifestDesc, name);
        }

        private List<string> GetRecursiveFileInfo(string resourceName, bool recursive)
        {
            return manifestDesc.catalog.GetRecursiveResourceName(manifestDesc, resourceName, recursive);
        }

        private IEnumerable<T> GetFileLocationFiles<T>(params FileLocation[] fileLocations) where T : ScriptSource, new()
        {
            List<string> Files = new List<string>();

            if (fileLocations != null)
            {
                foreach (FileLocation fileLocation in fileLocations)
                {
                    if (fileLocation != null)
                    {
                        if (fileLocation.Directory != null)
                        {
                            // Find all the files that correspond
                            // Need to change from a file name to a resource name
                            string Value = fileLocation.Directory.Value.Replace('\\', '.');

                            bool Recursive = true;

                            if (fileLocation.Directory.recursiveSpecified)
                            {
                                Recursive = fileLocation.Directory.recursive;
                            }

                            List<string> FileInfos = GetRecursiveFileInfo(Value, Recursive);
                            if (FileInfos != null)
                                Files.AddRange(FileInfos);
                        }

                        if (fileLocation.File != null)
                        {
                            // Need to change from a file name to a resource name
                            string FileInfo = this.GetFileInfo(fileLocation.File);
                            if (FileInfo == null)
                                throw new Exceptions.DatabaseManagementException("File could not be found in package");

                            Files.Add(FileInfo);
                        }
                    }
                }
            }

            return new ResourceFileReaderEnumerable<T>(Files, this);
        }

        private Stream GetResourceStream(string resourceName)
        {
            return manifestDesc.assembly.GetManifestResourceStream(resourceName);
        }

        #region IDBPackage Members

        private DatabaseManifest manifest;

        public DatabaseManifest Manifest
        {
            get
            {
                return manifest;
            }
        }

        public IEnumerable<SqlSchemaChangeFamily> GetSchemaChanges()
        {
            return new ResourceSchemaFamilyEnumerable(this);
        }

        public IEnumerable<SqlSource> GetViews()
        {
            return GetFileLocationFiles<SqlSource>(Manifest.Views);
        }

        public IEnumerable<SqlSource> GetJobs()
        {
            if (Manifest.Jobs != null)
                return GetFileLocationFiles<SqlSource>(Manifest.Jobs.Scripts);
            return GetFileLocationFiles<SqlSource>(null);
        }

        public IEnumerable<SqlSource> GetXMLJobs()
        {
            if (Manifest.Jobs != null)
                return GetFileLocationFiles<SqlSource>(Manifest.Jobs.SQLJobDefinitions);
            return GetFileLocationFiles<SqlSource>(null);
        }

        public IEnumerable<SqlSource> GetTriggers()
        {
            return GetFileLocationFiles<SqlSource>(Manifest.Triggers);
        }

        public IEnumerable<SqlSource> GetFunctions()
        {
            return GetFileLocationFiles<SqlSource>(Manifest.Functions);
        }

        public IEnumerable<SqlSource> GetPreSteps()
        {
            return GetFileLocationFiles<SqlSource>(Manifest.PreSteps);
        }

        public IEnumerable<SqlSource> GetPostSteps()
        {
            return GetFileLocationFiles<SqlSource>(Manifest.PostSteps);
        }

        public IEnumerable<SqlSource> GetStoredProcedures()
        {
            return GetSprocsLocationFiles<SqlSource>(Manifest.Procedures);
        }

        private Grants[] GetSprocGants(FileLocationReference fileLocationReference)
        {
            List<string> Files = new List<string>();
            if (fileLocationReference != null)
            {
                if (fileLocationReference.File != null)
                {
                    // Need to change from a file name to a resource name
                    string FileInfo = this.GetFileInfo(fileLocationReference.File);
                    if (FileInfo == null)
                    {
                        throw new Exceptions.DatabaseManagementException("Stored Procedure list File could not be found in package");
                    }

                    Files.Add(FileInfo);
                }
            }

            IEnumerable<SqlSource> sources = new ResourceFileReaderEnumerable<SqlSource>(Files, this);
            List<Grants> list = new List<Grants>();
            foreach (SqlSource source in sources)
            {
                XmlSerializer Serializer = new XmlSerializer(typeof(Grants));
                Grants grants = (Grants)Serializer.Deserialize(source.Stream);
                if (grants != null)
                {
                    list.Add(grants);
                }
                else
                {
                    throw new Exception(string.Format("Failed to serialize the grant list file [{0}]", source.Name));
                }
            }
            return list.ToArray();
        }

        private IEnumerable<T> GetSprocsLocationFiles<T>(FileLocationReference fileLocationReference) where T : ScriptSource, new()
        {
            Grants[] grants = GetSprocGants(fileLocationReference);
            List<string> Files = new List<string>();
            foreach (Grants grant in grants)
            {
                foreach (StoredProcedure grantsSecurable in grant.SQLObjects.StoredProcedures)
                {
                    Files.Add(grantsSecurable.Source);
                }
            }
            return new StoredProcedureFileReaderEnumerable<T>(Files, this);
        }

        public SortedDictionary<string, StoredProcedureDefinition> GetStoredProceduresWithGrants()
        {
            return GetFileLocationContent(Manifest.Procedures);
        }

        private SortedDictionary<string, StoredProcedureDefinition> GetFileLocationContent(FileLocationReference fileLocationReference)
        {
            SortedDictionary<string, StoredProcedureDefinition> sprocs = new SortedDictionary<string, StoredProcedureDefinition>();
            Grants[] grants = GetSprocGants(fileLocationReference);
            List<string> Files = new List<string>();
            foreach (Grants grant in grants)
            {
                if (grant.SQLObjects != null && grant.SQLObjects.StoredProcedures != null)
                {
                    foreach (StoredProcedure grantsSecurable in grant.SQLObjects.StoredProcedures)
                    {
                        Files.Add(grantsSecurable.Source);
                    }
                }
            }

            StoredProcedureFileReaderEnumerable<SqlSource> sources = new StoredProcedureFileReaderEnumerable<SqlSource>(Files, this);

            SortedDictionary<string, SqlSource> dict = new SortedDictionary<string, SqlSource>();
            foreach (SqlSource source in sources)
            {
                dict.Add(source.Name, source);
            }

            foreach (Grants grant in grants)
            {
                if (grant != null && grant.SQLObjects != null)
                {
                    foreach (StoredProcedure gSecurable in grant.SQLObjects.StoredProcedures)
                    {
                        StoredProcedureDefinition def = new StoredProcedureDefinition(dict[gSecurable.Source], gSecurable);
                        if (!(sprocs.ContainsKey(gSecurable.Name)))
                        {
                            sprocs.Add(gSecurable.Name, def);
                        }
                        else
                        {
                            //need to be fixed
                        }
                    }
                }
            }
            return sprocs;
        }

        public IEnumerable<SqlSource> GetReplicationScripts()
        {
            if (Manifest.Replication != null)
                return GetFileLocationFiles<SqlSource>(Manifest.Replication.Scripts);
            return GetFileLocationFiles<SqlSource>(null);
        }

        public IEnumerable<ScriptSource> GetReplicationDefinitions()
        {
            return GetFileLocationFiles<ScriptSource>(Manifest.Replication.ReplicationDefinitions);
        }

        public SortedDictionary<string, SQLJobDefinition> GetManifestSQLJobData()
        {
            SortedDictionary<string, SQLJobDefinition> DBQLJobData = new SortedDictionary<string, SQLJobDefinition>();
            SQLJobXMLHelper sqlJobXMLHelper = new SQLJobXMLHelper();
            if (Manifest.Jobs != null)
            {
                foreach (ScriptSource source in GetFileLocationFiles<ScriptSource>(Manifest.Jobs.SQLJobDefinitions))
                {
                    SQLJobDefinition sqlJobDefinition = sqlJobXMLHelper.Load(source);
                    DBQLJobData.Add(sqlJobDefinition.JobName, sqlJobDefinition);
                }
            }
            return DBQLJobData;
        }

        public string GetDatabaseName()
        {
            return Manifest.name;
        }

        public string GetPackageName()
        {
            return Manifest.packageName;
        }

        public string GetPackageDescription()
        {
            return Manifest.description;
        }

        public string GetPackageVersion()
        {
            return Manifest.version;
        }

        public Guid GetDatabaseId()
        {
            return Manifest.id;
        }

        public IEnumerable<SqlSource> GetConfigurationScripts()
        {
            return GetFileLocationFiles<SqlSource>(Manifest.Configuration);
        }

        public IEnumerable<ScriptSource> GetResourceScripts(string type)
        {
            List<FileLocation> files = new List<FileLocation>();
            foreach (TypedFileLocation loc in Manifest.Resources)
            {
                if (loc.type.Equals(type))
                {
                    files.AddRange(loc.Scripts);
                }
            }
            if (files.Count > 0)
            {
                return GetFileLocationFiles<ScriptSource>(files.ToArray());
            }
            else
            {
                return null;
            }
        }

        public bool NeedsCleanup
        {
            get
            {
                return needsCleanup;
            }
        }
        #endregion
    }
}
